package org.jasper.impl.container;

import org.jasper.api.base.KieBase;
import org.jasper.api.base.KieBaseConfiguration;
import org.jasper.api.container.Environment;
import org.jasper.api.container.InternalKieContainer;
import org.jasper.api.container.Message;
import org.jasper.api.container.ReleaseId;
import org.jasper.api.knowledge.InternalKnowledgeBase;
import org.jasper.api.knowledge.KnowledgeBuilder;
import org.jasper.api.knowledge.KnowledgePackage;
import org.jasper.api.module.InternalKieModule;
import org.jasper.api.project.KieProject;
import org.jasper.api.repository.KieRepository;
import org.jasper.api.runtime.KieRuntimeEventManager;
import org.jasper.api.service.InternalKieServices;
import org.jasper.api.service.KieServices;
import org.jasper.api.session.KieSession;
import org.jasper.api.session.KieSessionConfiguration;
import org.jasper.api.session.KieSessionModel;
import org.jasper.api.session.StatelessKieSession;
import org.jasper.constants.EventProcessingOption;
import org.jasper.constants.MBeansOption;
import org.jasper.impl.base.KieBaseModelImpl;
import org.jasper.impl.base.KnowledgeBaseFactory;
import org.jasper.impl.base.RuleBaseConfiguration;
import org.jasper.impl.compiler.bean.InjectionHelper;
import org.jasper.impl.knowledge.KnowledgePackageImpl;
import org.jasper.impl.management.DroolsManagementAgent;
import org.jasper.impl.module.AbstractKieModule;
import org.jasper.impl.session.KieSessionModelImpl;
import org.jasper.impl.session.StatefulKnowledgeSessionImpl;
import org.jasper.impl.utils.ResultsImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.ObjectName;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static org.jasper.impl.utils.Drools.isJndiAvailable;

/**
 * Created by OP038074 on 2018/7/3.
 */
public class KieContainerImpl implements InternalKieContainer {

    private static final Logger log    = LoggerFactory.getLogger(KieContainerImpl.class);
    private KieProject kProject;
    private final KieRepository kr;
    private final String containerId;
    private ReleaseId containerReleaseId;

    private final Map<String, KieBase> kBases = new ConcurrentHashMap<String, KieBase>();
    private final Map<String, KieSession> kSessions = new ConcurrentHashMap<String, KieSession>();
    private final Map<String, StatelessKieSession> statelessKSessions = new ConcurrentHashMap<String, StatelessKieSession>();

    public KieContainerImpl(String containerId, KieProject kProject, KieRepository kr) {
        this.kr = kr;
        this.kProject = kProject;
        this.containerId = containerId;
        kProject.init();
        initMBeans(containerId);
    }

    private void initMBeans(String containerId) {
        if ( isMBeanOptionEnabled() ) {
            KieContainerMonitor monitor = new KieContainerMonitor(this);
            ObjectName on = DroolsManagementAgent.createObjectNameBy(containerId);
            DroolsManagementAgent.getInstance().registerMBean( this, monitor, on );
        }
    }

    private boolean isMBeanOptionEnabled() {
        return MBeansOption.isEnabled(System.getProperty(MBeansOption.PROPERTY_NAME, MBeansOption.DISABLED.toString()));
    }

    public void dispose() {
        Set<DroolsManagementAgent.CBSKey> cbskeys = new HashSet<DroolsManagementAgent.CBSKey>();
        if ( isMBeanOptionEnabled() ) {
            for (Map.Entry<String, KieSession> kv : kSessions.entrySet()) {
                cbskeys.add(new DroolsManagementAgent.CBSKey(containerId, ((InternalKnowledgeBase) kv.getValue().getKieBase()).getId(), kv.getKey()));
            }
            for (Map.Entry<String, StatelessKieSession> kv : statelessKSessions.entrySet()) {
                cbskeys.add(new DroolsManagementAgent.CBSKey(containerId, ((InternalKnowledgeBase) kv.getValue().getKieBase()).getId(), kv.getKey()));
            }
        }

        for (KieSession kieSession : kSessions.values()) {
            kieSession.dispose();
        }
        kSessions.clear();
        statelessKSessions.clear();

        if ( isMBeanOptionEnabled() ) {
            for (DroolsManagementAgent.CBSKey c : cbskeys) {
                DroolsManagementAgent.getInstance().unregisterKnowledgeSessionBean(c);
            }
            for (KieBase kb : kBases.values()) {
                DroolsManagementAgent.getInstance().unregisterKnowledgeBase((InternalKnowledgeBase) kb);
            }
            DroolsManagementAgent.getInstance().unregisterMBeansFromOwner(this);
        }

        ((InternalKieServices) KieServices.Factory.get()).clearRefToContainerId(this.containerId, this);
    }

    public KieSession newKieSession(String kSessionName) {
        return newKieSession(kSessionName, null, null);
    }

    public KieSession newKieSession(String kSessionName, Environment environment, KieSessionConfiguration conf) {
        KieSessionModelImpl kSessionModel = kSessionName != null ?
                (KieSessionModelImpl) getKieSessionModel(kSessionName) :
                (KieSessionModelImpl) findKieSessionModel(false);

        if ( kSessionModel == null ) {
            log.error("Unknown KieSession name: " + kSessionName);
            return null;
        }
        if (kSessionModel.getType() == KieSessionModel.KieSessionType.STATELESS) {
            throw new RuntimeException("Trying to create a stateful KieSession from a stateless KieSessionModel: " + kSessionModel.getName());
        }
        KieBase kBase = getKieBase( kSessionModel.getKieBaseModel().getName() );
        if ( kBase == null ) {
            log.error("Unknown KieBase name: " + kSessionModel.getKieBaseModel().getName());
            return null;
        }

        KieSession kSession = kBase.newKieSession( conf != null ? conf : getKieSessionConfiguration( kSessionModel ), environment );
        if (isJndiAvailable()) {
            InjectionHelper.wireListnersAndWIHs(kSessionModel, kSession);
        }
        registerLoggers(kSessionModel, kSession);

        ((StatefulKnowledgeSessionImpl) kSession).initMBeans(containerId, ((InternalKnowledgeBase) kBase).getId(), kSessionModel.getName());

        kSessions.put(kSessionModel.getName(), kSession);
        return kSession;

    }

    private KieSessionConfiguration getKieSessionConfiguration( KieSessionModel kSessionModel ) {
        KieSessionConfiguration ksConf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
        ksConf.setOption( kSessionModel.getClockType() );
        ksConf.setOption( kSessionModel.getBeliefSystem() );
        return ksConf;
    }

    private void registerLoggers(KieSessionModelImpl kSessionModel, KieRuntimeEventManager kSession) {

    }

    public KieBase getKieBase(String kBaseName) {
        KieBase kBase = kBases.get( kBaseName );
        if ( kBase == null ) {
            KieBaseModelImpl kBaseModel = getKieBaseModelImpl(kBaseName);
            synchronized (kBaseModel) {
                kBase = kBases.get( kBaseName );
                if ( kBase == null ) {
                    ResultsImpl msgs = new ResultsImpl();
                    kBase = createKieBase(kBaseModel, kProject, msgs, null);
                    if (kBase == null) {
                        // build error, throw runtime exception
                        throw new RuntimeException("Error while creating KieBase" + msgs.filterMessages(Message.Level.ERROR));
                    }
                    kBases.put(kBaseName, kBase);
                }
            }
        }
        return kBase;
    }

    private KieBaseModelImpl getKieBaseModelImpl(String kBaseName) {
        KieBaseModelImpl kBaseModel = (KieBaseModelImpl) kProject.getKieBaseModel(kBaseName);
        if (kBaseModel == null) {
            throw new RuntimeException( "The requested KieBase \"" + kBaseName + "\" does not exist" );
        }
        return kBaseModel;
    }

    public KieSessionModel getKieSessionModel(String kSessionName) {
        return kProject.getKieSessionModel(kSessionName);
    }

    private KieSessionModel findKieSessionModel(boolean stateless) {
        KieSessionModel defaultKieSessionModel = stateless ? kProject.getDefaultStatelessKieSession() : kProject.getDefaultKieSession();
        if (defaultKieSessionModel == null) {
            throw new RuntimeException(stateless ? "Cannot find a default StatelessKieSession" : "Cannot find a default KieSession");
        }
        return defaultKieSessionModel;
    }

    private KieBase createKieBase(KieBaseModelImpl kBaseModel, KieProject kieProject, ResultsImpl messages, KieBaseConfiguration conf) {
        InternalKieModule kModule = kieProject.getKieModuleForKBase( kBaseModel.getName() );
        Collection<KnowledgePackage> pkgs = kModule.getKnowledgePackagesForKieBase(kBaseModel.getName());

        if ( pkgs == null ) {
            KnowledgeBuilder kbuilder = AbstractKieModule.buildKnowledgePackages(kBaseModel, kieProject, messages);
            if ( kbuilder.hasErrors() ) {
                // Messages already populated by the buildKnowlegePackages
                return null;
            }
        }

        // if we get to here, then we know the pkgs is now cached
        pkgs = kModule.getKnowledgePackagesForKieBase(kBaseModel.getName());

        if ( kBaseModel.getEventProcessingMode() == EventProcessingOption.CLOUD &&
                (conf == null || conf.getOption(EventProcessingOption.class) == EventProcessingOption.CLOUD ) ) {
            for (KnowledgePackage kpkg : pkgs) {
                if ( ((KnowledgePackageImpl) kpkg).needsStreamMode() ) {
                    throw new RuntimeException( "The requested KieBase \"" + kBaseModel.getName() + "\" has been set to run in CLOUD mode but requires features only available in STREAM mode" );
                }
            }
        }

        ClassLoader cl = kieProject.getClassLoader();
        if (conf == null) {
            conf = getKnowledgeBaseConfiguration(kBaseModel, cl);
        } else if (conf instanceof RuleBaseConfiguration) {
            ((RuleBaseConfiguration)conf).setClassLoader(cl);
        }
        InternalKnowledgeBase kBase = (InternalKnowledgeBase) KnowledgeBaseFactory.newKnowledgeBase( kBaseModel.getName(), conf );
        kBase.setResolvedReleaseId(containerReleaseId);
        kBase.setContainerId(containerId);
        kBase.initMBeans();

        kBase.addKnowledgePackages( pkgs );
        return kBase;
    }

    private KieBaseConfiguration getKnowledgeBaseConfiguration(KieBaseModelImpl kBaseModel, ClassLoader cl) {
        KieBaseConfiguration kbConf = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(null, cl);
        kbConf.setOption(kBaseModel.getEqualsBehavior());
        kbConf.setOption(kBaseModel.getEventProcessingMode());
        kbConf.setOption(kBaseModel.getDeclarativeAgenda());
        return kbConf;
    }
}
